import fs from 'fs'
import path from 'path'
import crypto from 'crypto'
import {Readable} from 'node:stream'
import lodash from 'lodash-es'
import PluginWorksResponseDTO from './lsLib/model/dto/PluginWorksResponseDTO.mjs'
import WorksSet from './lsLib/model/entity/WorksSet.mjs'
import PluginCreateTaskResponseDTO from "./lsLib/model/dto/PluginCreateTaskResponseDTO.mjs";
import PathTypeEnum from "./lsLib/constant/PathTypeEnum.mjs";
import AuthorRank from "./lsLib/constant/AuthorRank.mjs";
import PluginStreamCreateTaskResponseDTO from "./lsLib/model/dto/PluginStreamCreateTaskResponseDTO.mjs";
import PluginCreateParentTaskResponseDTO from "./lsLib/model/dto/PluginCreateParentTaskResponseDTO.mjs";

export default class LocalTaskHandler {
  /**
   * 插件工具
   * @type { PluginTool }
   */
  pluginTool

  constructor(pluginTool) {
    this.pluginTool = pluginTool
  }

  /**
   * 创建任务
   * @param url 需解析的url
   * @return 根据解析结果创建的任务数组
   */
  async create(url) {
    // 创建任务流
    return new TaskStream(url, this.pluginTool)
  }

  /**
   * 生成作品信息
   * @param task {Task} 任务信息
   * @return {Promise<PluginWorksResponseDTO>}
   */
  async createWorksInfo(task) {
    // 保存在pluginData的数据
    const pluginData = task.pluginData
    // 含义列表
    const meaningOfPaths = pluginData.meaningOfPaths
    // 文件名
    const filename = path.parse(task.url).name
    // 其他文件信息
    const stats = await fs.promises.stat(task.url)

    const response = new PluginWorksResponseDTO()

    // 处理pluginData里的信息
    // 处理作品集
    const worksSetInfos = this.getDataFromMeaningOfPath(meaningOfPaths, PathTypeEnum.WORKS_SET_NAME)
    if (worksSetInfos.length > 0) {
      response.worksSets = []
      worksSetInfos.forEach((worksSetInfo) => {
        const worksSet = new WorksSet()
        worksSet.siteWorksSetName = worksSetInfo.name
        worksSet.siteWorksSetId = worksSetInfo.details.siteWorksSetId
        response.worksSets.push(worksSet)
      })
    }
    // 处理作品名称
    const worksNames = this.getDataFromMeaningOfPath(meaningOfPaths, PathTypeEnum.WORKS_NAME)
    // 如果pluginData里保存的用户解释含义中包含worksName，则使用worksName，否则使用文件名
    if (worksNames.length > 0) {
      response.works.siteWorksName = worksNames[0].name
    } else {
      response.works.siteWorksName = filename
    }
    // siteWorksId
    response.works.siteWorksId = task.siteWorksId
    // 处理标签
    const tagInfos = this.getDataFromMeaningOfPath(meaningOfPaths, PathTypeEnum.TAG)
    response.localTags = tagInfos.map((tagInfo) => tagInfo.details)
    // 处理作者信息
    const authors = this.getDataFromMeaningOfPath(meaningOfPaths, PathTypeEnum.AUTHOR)
    response.localAuthors = authors.map((authorInfo) => authorInfo.details)

    // 处理资源大小
    response.resource.resourceSize = stats.size
    // 处理是否可续传
    response.resource.continuable = true

    return response
  }

  /**
   * 获取用于开始下载任务的资源
   * @param task {Task} 需要开始的任务
   * @returns {Promise<PluginWorksResponseDTO>}
   */
  async start(task) {
    const response = new PluginWorksResponseDTO()
    // 文件名
    const filename = path.parse(task.url).name
    // 其他文件信息
    const stats = await fs.promises.stat(task.url)
    // 扩展名
    const filenameExtension = path.parse(task.url).ext
    // 资源
    response.resource.resourceStream = fs.createReadStream(task.url)
    response.resource.filenameExtension = filenameExtension
    response.resource.resourceSize = stats.size
    response.resource.continuable = true
    return response
  }

  /**
   * 重试下载任务
   * 重试下载任务
   * @param tasks {Task} 需要重试的任务
   * @return {Promise<PluginWorksResponseDTO>}
   */
  retry(tasks) {
    return this.start(tasks)
  }

  /**
   * 暂停下载任务
   * @param taskResParam { PluginTaskResParam } 需要暂停的任务和资源数据
   */
  pause(taskResParam) {
    if (taskResParam.resourcePluginDTO.resourceStream !== undefined && taskResParam.resourcePluginDTO.resourceStream !== null) {
      taskResParam.resourcePluginDTO.resourceStream.pause()
    }
  }

  /**
   * 停止下载任务
   * @param taskResParam { PluginTaskResParam } 需要停止的任务和资源数据
   */
  stop(taskResParam) {
    if (taskResParam.resourcePluginDTO.resourceStream !== undefined && taskResParam.resourcePluginDTO.resourceStream !== null) {
      taskResParam.resourcePluginDTO.resourceStream.destroy()
    }
  }

  /**
   * 恢复下载任务
   * @param taskResParam { PluginTaskResParam } 需要恢复下载的任务和资源数据
   * @return Promise<PluginWorksResponseDTO>
   */
  async resume(taskResParam) {
    const result = new PluginWorksResponseDTO()
    let pausedStream = taskResParam.resourcePluginDTO.resourceStream
    if (pausedStream === undefined || pausedStream === null) {
      const bytesWritten = await fs.promises.stat(taskResParam.resourcePath).then((stats) => stats.size)
      if (bytesWritten === 0) {
        pausedStream = fs.createReadStream(taskResParam.task.url)
      } else {
        pausedStream = fs.createReadStream(taskResParam.task.url, { start: bytesWritten })
      }
    }
    const stats = await fs.promises.stat(taskResParam.task.url)
    result.resource.filenameExtension = path.parse(taskResParam.task.url).ext
    result.resource.resourceSize = stats.size
    result.resource.continuable = true
    result.resource.resourceStream = pausedStream
    result.resource.continuable = true
    result.resource.resourceSize = stats.size
    return result
  }

  /**
   * 在含义列表中查找对应类型的含义
   * @param meaningOfPaths 含义列表
   * @param type 类型
   * @return {*}
   */
  getDataFromMeaningOfPath(meaningOfPaths, type) {
    return meaningOfPaths.filter((meaningOfPath) => meaningOfPath.type === type)
  }
}

class TaskStream extends Readable {
  /**
   * 基础路径
   */
  directoryPath

  /**
   * 迭代栈
   */
  stack

  /**
   * 插件工具
   * @type { PluginTool }
   */
  pluginTool

  /**
   * 计数器（用于给作品集设置唯一标识）
   */
  counter

  constructor(directoryPath, pluginTool) {
    super({ objectMode: true, highWaterMark: 50 })
    this.directoryPath = directoryPath
    this.stack = [{ dir: this.directoryPath, meaningOfPaths: [] }]
    this.pluginTool = pluginTool
    this.counter = 0
  }

  async _read(_size) {
    this.counter++
    if (this.counter === 1) {
        const pluginStreamCreateTaskResponseDTO = new PluginStreamCreateTaskResponseDTO()
        const pluginTaskResponseDTO = new PluginCreateParentTaskResponseDTO()
        pluginStreamCreateTaskResponseDTO.taskType = 'parent'
        pluginStreamCreateTaskResponseDTO.task = pluginTaskResponseDTO
        pluginTaskResponseDTO.pluginTaskId = '1'
        pluginTaskResponseDTO.url = this.directoryPath
        pluginTaskResponseDTO.taskName = `从本地路径【${this.directoryPath}】创建的导入`
        pluginTaskResponseDTO.siteDomain = 'local'
        this.push(pluginStreamCreateTaskResponseDTO)
        return
    }
    let bufferFilled = false
    // 迭代到路径指向文件或者栈为空为止
    while (!bufferFilled) {
      try {
        // 获取并移除栈顶元素
        const dirInfo = this.stack.pop()
        if (dirInfo === undefined) {
          continue
        }
        const dir = dirInfo.dir
        const stats = await fs.promises.stat(dir)

        if (stats.isDirectory()) {
          // 请求用户解释目录含义
          const waitUserInput = this.pluginTool.explainPath(dir)
          const meaningOfPaths = await waitUserInput
          meaningOfPaths.forEach((meaningOfPath) => {
            // 如果含义是作品集，路径作为站点作品集id
            if (meaningOfPath.type === PathTypeEnum.WORKS_SET_NAME) {
              meaningOfPath.details = { siteWorksSetId: dir }
            }
            // 如果含义是作者，作者角色设置为RANK_0
            if (meaningOfPath.type === PathTypeEnum.AUTHOR) {
              meaningOfPath.details.authorRank = AuthorRank.RANK_0
            }
          })
          // 将获得的目录含义并入从上级目录继承的含义数组中
          dirInfo.meaningOfPaths = [...dirInfo.meaningOfPaths, ...meaningOfPaths]

          // 如果是目录，则获取子项并按相反顺序压入栈中（保证左子树先遍历）
          const entries = await fs.promises.readdir(dir, { withFileTypes: true })
          for (let i = entries.length - 1; i >= 0; i--) {
            const entry = entries[i]
            // 把下级目录放进栈的同时，将自身的含义传给下级目录
            const childDir = path.join(dir, entry.name)
            const childMeaningOfPaths = lodash.cloneDeep(dirInfo.meaningOfPaths)
            const childInfo = lodash.cloneDeep({
              dir: childDir,
              meaningOfPaths: childMeaningOfPaths
            })
            this.stack.push(childInfo)
          }
        } else {
          // 如果是文件，则创建任务并加入列表
          const pluginStreamCreateTaskResponseDTO = new PluginStreamCreateTaskResponseDTO()
          const pluginTaskResponseDTO = new PluginCreateTaskResponseDTO()
          pluginStreamCreateTaskResponseDTO.taskType = 'child'
          pluginStreamCreateTaskResponseDTO.task = pluginTaskResponseDTO
          pluginTaskResponseDTO.pluginPid = '1'
          pluginTaskResponseDTO.url = dir
          pluginTaskResponseDTO.taskName = path.parse(dir).base
          pluginTaskResponseDTO.siteDomain = 'local'
          pluginTaskResponseDTO.siteWorksId = await this.calculateSHA256(dir)
          pluginTaskResponseDTO.pluginData = { meaningOfPaths: dirInfo.meaningOfPaths }
          pluginTaskResponseDTO.continuable = true
          this.push(pluginStreamCreateTaskResponseDTO)
          bufferFilled = true
        }
      } catch (err) {
        this.emit('error', err)
      } finally {
        if (this.stack.length === 0) {
          this.push(null)
          bufferFilled = true
        }
      }
    }
  }

  calculateSHA256(fileLocation) {
    return new Promise((resolve, reject) => {
      const hash = crypto.createHash('sha256');
      const stream = fs.createReadStream(fileLocation);

      stream.on('data', (data) => {
        hash.update(data);
      });

      stream.on('end', () => {
        resolve(hash.digest('hex'));
      });

      stream.on('error', (err) => {
        reject(err);
      });
    });
  }
}
